def hex_to_rgb(hex_color):
    """Converts a hex color code to an RGB tuple (0-255).

    Args:
        hex_color: A hex color string (e.g., "#RRGGBB" or "RRGGBB").

    Returns:
        An RGB tuple (r, g, b) where r, g, b are integers from 0 to 255.
        Returns None if the hex color code is invalid.
    """
    hex_color = hex_color.lstrip('#')
    if len(hex_color) != 6:
        return None
    try:
        return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
    except ValueError:
        return None

def rgb_to_hex(rgb_color):
    """Converts an RGB tuple (0-255) to a hex color code.

    Args:
        rgb_color: An RGB tuple (r, g, b) where r, g, b are integers from 0 to 255.

    Returns:
        A hex color string (e.g., "#RRGGBB").
    """
    return '#{:02x}{:02x}{:02x}'.format(*rgb_color)

def generate_gradient_colors(base_colors, num_steps):
    """Generates a list of gradient colors between the given base colors.

    Args:
        base_colors: A list of base colors. Each color can be an RGB tuple (0-255) or a hex color string.
        num_steps: The total number of colors in the gradient, including base colors.

    Returns:
        A list of RGB tuples (0-255) representing the gradient colors.
    """
    if not isinstance(base_colors, list) or len(base_colors) < 2:
        raise ValueError("base_colors must be a list containing at least two colors.")
    if not isinstance(num_steps, int) or num_steps < 2:
        raise ValueError("num_steps must be an integer greater than or equal to 2.")

    rgb_base_colors = []
    for color in base_colors:
        if isinstance(color, str):
            rgb = hex_to_rgb(color)
            if rgb is None:
                raise ValueError(f"Invalid hex color code: {color}")
            rgb_base_colors.append(rgb)
        elif isinstance(color, tuple) and len(color) == 3 and all(0 <= c <= 255 for c in color):
            rgb_base_colors.append(color)
        else:
            raise ValueError(f"Invalid color format: {color}. Colors must be RGB tuples or hex color codes.")

    gradient_colors = []
    num_segments = len(rgb_base_colors) - 1
    steps_per_segment = (num_steps - 1) // num_segments # Integer division to distribute steps
    remainder_steps = (num_steps - 1) % num_segments # Remaining steps to distribute

    for i in range(num_segments):
        start_color = rgb_base_colors[i]
        end_color = rgb_base_colors[i+1]
        segment_steps = steps_per_segment + (1 if i < remainder_steps else 0) + 1 # +1 to include both start and end color in segment

        if segment_steps <= 1:
            segment_gradient = [start_color, end_color] # Just return start and end if segment_steps is too small
        else:
            segment_gradient = []
            for step in range(segment_steps):
                ratio = step / (segment_steps - 1)
                r = int(start_color[0] + (end_color[0] - start_color[0]) * ratio)
                g = int(start_color[1] + (end_color[1] - start_color[1]) * ratio)
                b = int(start_color[2] + (end_color[2] - start_color[2]) * ratio)
                segment_gradient.append((r, g, b))

        if i < num_segments - 1: # Avoid adding the last color twice except for the very last segment
            gradient_colors.extend(segment_gradient[:-1]) # Exclude last color to avoid duplication
        else:
            gradient_colors.extend(segment_gradient) # Include last color for the final segment


    return gradient_colors

if __name__ == '__main__':
    # Example Usage

    # Example 1: Gradient between two colors (RGB tuples)
    base_colors_rgb = [(255, 0, 0), (0, 0, 255)] # Red to Blue
    num_colors_1 = 5
    gradient_rgb_1 = generate_gradient_colors(base_colors_rgb, num_colors_1)
    print(f"RGB Gradient ({num_colors_1} colors) from {base_colors_rgb}:")
    print(gradient_rgb_1)
    print([rgb_to_hex(color) for color in gradient_rgb_1]) # Convert to hex for easier viewing

    print("-" * 50)

    # Example 2: Gradient between three colors (Hex codes)
    base_colors_hex = ["#FF0000", "#00FF00", "#0000FF"] # Red to Green to Blue
    num_colors_2 = 7
    gradient_rgb_2 = generate_gradient_colors(base_colors_hex, num_colors_2)
    print(f"RGB Gradient ({num_colors_2} colors) from {base_colors_hex}:")
    print(gradient_rgb_2)
    print([rgb_to_hex(color) for color in gradient_rgb_2]) # Convert to hex for easier viewing

    print("-" * 50)

    # Example 3: Gradient with more steps and mixed color formats
    base_colors_mixed = ["#FFFF00", (0, 128, 0), "#800080"] # Yellow to Green to Purple
    num_colors_3 = 15
    gradient_rgb_3 = generate_gradient_colors(base_colors_mixed, num_colors_3)
    print(f"RGB Gradient ({num_colors_3} colors) from {base_colors_mixed}:")
    print(gradient_rgb_3)
    print([rgb_to_hex(color) for color in gradient_rgb_3]) # Convert to hex for easier viewing

    print("-" * 50)

    # Example 4: Only two colors, more steps
    base_colors_simple = ["#FFFFFF", "#000000"] # White to Black
    num_colors_4 = 10
    gradient_rgb_4 = generate_gradient_colors(base_colors_simple, num_colors_4)
    print(f"RGB Gradient ({num_colors_4} colors) from {base_colors_simple}:")
    print(gradient_rgb_4)
    print([rgb_to_hex(color) for color in gradient_rgb_4]) # Convert to hex for easier viewing